/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.ipudong.widget.numberpicker;

import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.LayoutInflater.Filter;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.animation.DecelerateInterpolator;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.ipudong.widget.R;

import java.text.DecimalFormatSymbols;
import java.util.Locale;


/**
 * A widget that enables the user to select a number form a predefined range.
 * There are two flavors of this widget and which one is presented to the user
 * depends on the current theme.
 * <ul>
 * <li>
 * If the current theme is derived from {@link android.R.style#Theme} the widget
 * presents the current value as an editable input field with an increment button
 * above and a decrement button below. Long pressing the buttons allows for a quick
 * change of the current value. Tapping on the input field allows to type in
 * a desired value.
 * </li>
 * <li>
 * If the current theme is derived from {@link android.R.style#Theme_Holo} or
 * {@link android.R.style#Theme_Holo_Light} the widget presents the current
 * value as an editable input field with a lesser value above and a greater
 * value below. Tapping on the lesser or greater value selects it by animating
 * the number axis up or down to make the chosen value current. Flinging up
 * or down allows for multiple increments or decrements of the current value.
 * Long pressing on the lesser and greater values also allows for a quick change
 * of the current value. Tapping on the current value allows to type in a
 * desired value.
 * </li>
 * </ul>
 * <p>
 * For an example of using this widget, see {@link android.widget.TimePicker}.
 * </p>
 */
//@Widget
public class NumberPicker extends LinearLayout {

	/**
	 * The number of items show in the selector wheel.
	 */
	private static final int SELECTOR_WHEEL_ITEM_COUNT = 3;

	/**
	 * The default update interval during long press.
	 */
	private static final long DEFAULT_LONG_PRESS_UPDATE_INTERVAL = 300;

	/**
	 * The index of the middle selector item.
	 */
	private static final int SELECTOR_MIDDLE_ITEM_INDEX = SELECTOR_WHEEL_ITEM_COUNT / 2;

	/**
	 * The coefficient by which to adjust (divide) the max fling velocity.
	 */
	private static final int SELECTOR_MAX_FLING_VELOCITY_ADJUSTMENT = 8;

	/**
	 * The the duration for adjusting the selector wheel.
	 */
	private static final int SELECTOR_ADJUSTMENT_DURATION_MILLIS = 800;

	/**
	 * The duration of scrolling while snapping to a given position.
	 */
	private static final int SNAP_SCROLL_DURATION = 300;

	/**
	 * The strength of fading in the top and bottom while drawing the selector.
	 */
	private static final float TOP_AND_BOTTOM_FADING_EDGE_STRENGTH = 0.9f;

	/**
	 * The default unscaled height of the selection divider.
	 */
	private static final int UNSCALED_DEFAULT_SELECTION_DIVIDER_HEIGHT = 2;

	/**
	 * The default unscaled distance between the selection dividers.
	 */
	private static final int UNSCALED_DEFAULT_SELECTION_DIVIDERS_DISTANCE = 48;

	/**
	 * The resource id for the default layout.
	 */
	// robert diff .
	private static final int DEFAULT_LAYOUT_RESOURCE_ID = 0;

	/**
	 * Constant for unspecified size.
	 */
	private static final int SIZE_UNSPECIFIED = -1;

	/**
	 * defalut text size.
	 */
	// robert diff .
	private static final int DEFAULT_INPUT_TEXT_SIZE = 20;

	/**
	 * default virtual button text size.
	 */
	// robert diff .
	private static final int DEFAULT_VIRTUAL_BUTTON_TEXT_SIZE = 12;

	/**
	 * default virtual button text alpha.
	 */
	// robert diff .
	private static final int DEFAULT_VIRTUAL_BUTTON_TEXT_ALPHA = 180;

	/**
	 * Use a custom NumberPicker formatting callback to use two-digit minutes
	 * strings like "01". Keeping a static formatter etc. is the most efficient
	 * way to do this; it avoids creating temporary objects on every call to
	 * format().
	 */
	private static class TwoDigitFormatter implements Formatter {
		final StringBuilder mBuilder = new StringBuilder ();

		char mZeroDigit;
		java.util.Formatter mFmt;

		final Object[] mArgs = new Object[1];

		TwoDigitFormatter () {
			final Locale locale = Locale.getDefault ();
			init (locale);
		}

		private void init (Locale locale) {
			mFmt = createFormatter (locale);
			mZeroDigit = getZeroDigit (locale);
		}

		public String format (int value) {
			final Locale currentLocale = Locale.getDefault ();
			if (mZeroDigit != getZeroDigit (currentLocale)) {
				init (currentLocale);
			}
			mArgs[0] = value;
			mBuilder.delete (0, mBuilder.length ());
			mFmt.format ("%02d", mArgs);
			return mFmt.toString ();
		}

		private static char getZeroDigit (Locale locale) {
			// return LocaleData.get(locale).zeroDigit;
			// robert diff .
			return new DecimalFormatSymbols (locale).getZeroDigit ();
		}

		private java.util.Formatter createFormatter (Locale locale) {
			return new java.util.Formatter (mBuilder, locale);
		}
	}

	private static final TwoDigitFormatter sTwoDigitFormatter = new TwoDigitFormatter ();

	// robert diff .
	public static Formatter getTwoDigitFormatter () {
		return sTwoDigitFormatter;
	}

	/**
	 * The increment button.
	 */
	private final ImageButton mIncrementButton;

	/**
	 * The decrement button.
	 */
	private final ImageButton mDecrementButton;

	/**
	 * The text for showing the current value.
	 */
	private final TextView mCenterValueLocationHelper;

	/**
	 * The distance between the two selection dividers.
	 */
	private final int mSelectionDividersDistance;

	/**
	 * The min height of this widget.
	 */
	private final int mMinHeight;

	/**
	 * The max height of this widget.
	 */
	private final int mMaxHeight;

	/**
	 * The max width of this widget.
	 */
	private final int mMinWidth;

	/**
	 * The max width of this widget.
	 */
	private int mMaxWidth;

	/**
	 * Flag whether to compute the max width.
	 */
	private final boolean mComputeMaxWidth;

	/**
	 * The height of the text.
	 */
	private final int mTextSize;

	/**
	 * The height of the text in virtual (increment/decrement) buttons
	 */
	// robert diff .
	private final int mVirtualButtonTextSize;

	/**
	 * The alpha of the text in virtual (increment/decrement) buttons
	 */
	// robert diff .
	private final int mVirtualButtonTextAlpha;

	/**
	 * The height of the gap between text elements if the selector wheel.
	 */
	private int mSelectorTextGapHeight;

	/**
	 * The values to be displayed instead the indices.
	 */
	private String[] mDisplayedValues;

	/**
	 * Lower value of the range of numbers allowed for the NumberPicker
	 */
	private int mMinValue;

	/**
	 * Upper value of the range of numbers allowed for the NumberPicker
	 */
	private int mMaxValue;

	/**
	 * Current value of this NumberPicker
	 */
	private int mValue;

	/**
	 * Listener to be notified upon current value change.
	 */
	private OnValueChangeListener mOnValueChangeListener;

	/**
	 * Listener to be notified upon scroll state change.
	 */
	private OnScrollListener mOnScrollListener;

	/**
	 * Formatter for for displaying the current value.
	 */
	private Formatter mFormatter;

	/**
	 * The speed for updating the value form long press.
	 */
	private long mLongPressUpdateInterval = DEFAULT_LONG_PRESS_UPDATE_INTERVAL;

	/**
	 * Cache for the string representation of selector indices.
	 */
	private final SparseArray<String> mSelectorIndexToStringCache = new SparseArray<> ();

	/**
	 * The selector indices whose value are show by the selector.
	 */
	private final int[] mSelectorIndices = new int[SELECTOR_WHEEL_ITEM_COUNT];

	/**
	 * The {@link Paint} for drawing the selector.
	 */
	private final Paint mSelectorWheelPaint;

	/**
	 * The height of a selector element (text + gap).
	 */
	private int mSelectorElementHeight;

	/**
	 * The initial offset of the scroll selector.
	 */
	private int mInitialScrollOffset = Integer.MIN_VALUE;

	/**
	 * The current offset of the scroll selector.
	 */
	private int mCurrentScrollOffset;

	/**
	 * The {@link Scroller} responsible for flinging the selector.
	 */
	private final Scroller mFlingScroller;

	/**
	 * The {@link Scroller} responsible for adjusting the selector.
	 */
	private final Scroller mAdjustScroller;

	/**
	 * The previous Y coordinate while scrolling the selector.
	 */
	private int mPreviousScrollerY;

	/**
	 * Handle to the reusable command for changing the current value from long
	 * press by one.
	 */
	private ChangeCurrentByOneFromLongPressCommand mChangeCurrentByOneFromLongPressCommand;

	/**
	 * The Y position of the last down event.
	 */
	private float mLastDownEventY;

	/**
	 * The time of the last down event.
	 */
	private long mLastDownEventTime;

	/**
	 * The Y position of the last down or move event.
	 */
	private float mLastDownOrMoveEventY;

	/**
	 * Determines speed during touch scrolling.
	 */
	private VelocityTracker mVelocityTracker;

	/**
	 * @see ViewConfiguration#getScaledTouchSlop()
	 */
	private int mTouchSlop;

	/**
	 * @see ViewConfiguration#getScaledMinimumFlingVelocity()
	 */
	private int mMinimumFlingVelocity;

	/**
	 * @see ViewConfiguration#getScaledMaximumFlingVelocity()
	 */
	private int mMaximumFlingVelocity;

	/**
	 * Flag whether the selector should wrap around.
	 */
	private boolean mWrapSelectorWheel;

	/**
	 * The back ground color used to optimize scroller fading.
	 */
	private final int mSolidColor;

	/**
	 * Flag whether this widget has a selector wheel.
	 */
	private final boolean mHasSelectorWheel;

	/**
	 * Divider for showing item to be selected while scrolling
	 */
	private final Drawable mSelectionDivider;

	/**
	 * The height of the selection divider.
	 */
	private final int mSelectionDividerHeight;

	/**
	 * The current scroll state of the number picker.
	 */
	private int mScrollState = OnScrollListener.SCROLL_STATE_IDLE;

	/**
	 * Flag whether to ignore move events - we ignore such when we show in IME
	 * to prevent the content from scrolling.
	 */
	// robert diff .
	private boolean mIngnoreMoveEvents;

	/**
	 * Flag whether to show soft input on tap.
	 */
	// robert diff .
	private boolean mShowSoftInputOnTap;

	/**
	 * The top of the top selection divider.
	 */
	private int mTopSelectionDividerTop;

	/**
	 * The bottom of the bottom selection divider.
	 */
	private int mBottomSelectionDividerBottom;

	/**
	 * The virtual id of the last hovered child.
	 */
	private int mLastHoveredChildVirtualViewId;

	/**
	 * Whether the increment virtual button is pressed.
	 */
	private boolean mIncrementVirtualButtonPressed;

	/**
	 * Whether the decrement virtual button is pressed.
	 */
	private boolean mDecrementVirtualButtonPressed;

	/**
	 * Helper class for managing pressed state of the virtual buttons.
	 */
	private final PressedStateHelper mPressedStateHelper;

	/**
	 * The keycode of the last handled DPAD down event.
	 */
	private int mLastHandledDownDpadKeyCode = -1;

	private final int mUnselectedTextColor;
	private int mTextColor;

	private final int middleTextGraity;
	private final int middleTextGraityUnit;

	private final int MID_TEXT_GRAITY_NORMAL = 0;
	private final int MID_TEXT_GRAITY_LEFT = 1;
	private final int MID_TEXT_GRAITY_RIGHT = 2;

	/**
	 * Interface to listen for changes of the current value.
	 */
	public interface OnValueChangeListener {

		/**
		 * Called upon a change of the current value.
		 *
		 * @param picker The NumberPicker associated with this listener.
		 * @param oldVal The previous value.
		 * @param newVal The new value.
		 */
		void onValueChange (NumberPicker picker, int oldVal, int newVal);
	}

	/**
	 * Interface to listen for the picker scroll state.
	 */
	public interface OnScrollListener {

		/**
		 * The view is not scrolling.
		 */
		int SCROLL_STATE_IDLE = 0;

		/**
		 * The user is scrolling using touch, and his finger is still on the screen.
		 */
		int SCROLL_STATE_TOUCH_SCROLL = 1;

		/**
		 * The user had previously been scrolling using touch and performed a fling.
		 */
		int SCROLL_STATE_FLING = 2;

		/**
		 * Callback invoked while the number picker scroll state has changed.
		 *
		 * @param view        The view whose scroll state is being reported.
		 * @param scrollState The current scroll state. One of
		 *                    {@link #SCROLL_STATE_IDLE},
		 *                    {@link #SCROLL_STATE_TOUCH_SCROLL} or
		 *                    {@link #SCROLL_STATE_IDLE}.
		 */
		void onScrollStateChange (NumberPicker view, int scrollState);
	}

	/**
	 * Interface used to format current value into a string for presentation.
	 */
	public interface Formatter {

		/**
		 * Formats a string representation of the current value.
		 *
		 * @param value The currently selected value.
		 * @return A formatted string representation.
		 */
		String format (int value);
	}

	/**
	 * Create a new number picker.
	 *
	 * @param context The application environment.
	 */
	public NumberPicker (Context context) {
		this (context, null);
	}

	/**
	 * Create a new number picker.
	 *
	 * @param context The application environment.
	 * @param attrs   A collection of attributes.
	 */
	public NumberPicker (Context context, AttributeSet attrs) {
		this (context, attrs, R.attr.numberPickerStyle);
	}

	/**
	 * Create a new number picker
	 *
	 * @param context  the application environment.
	 * @param attrs    a collection of attributes.
	 * @param defStyle The default style to apply to this view.
	 */
	public NumberPicker (Context context, AttributeSet attrs, int defStyle) {
		super (context, attrs);

		// process style attributes
		// robert diff .
		TypedArray attributesArray = context.obtainStyledAttributes (
				attrs, R.styleable.NumberPicker, defStyle, 0);
		final int layoutResId = attributesArray.getResourceId (
				R.styleable.NumberPicker_internalLayout, DEFAULT_LAYOUT_RESOURCE_ID);

		mHasSelectorWheel = (layoutResId != DEFAULT_LAYOUT_RESOURCE_ID);

		mSolidColor = attributesArray.getColor (R.styleable.NumberPicker_solidColor, 0);

		mUnselectedTextColor = attributesArray.getColor (R.styleable.NumberPicker_unselectedTextColor, 0);

		int mUnselectedTextSize = attributesArray.getDimensionPixelSize (
				R.styleable.NumberPicker_unselectedTextSize, 22);

		middleTextGraity = attributesArray.getInt (R.styleable.NumberPicker_middleTextGraity, MID_TEXT_GRAITY_NORMAL);

		middleTextGraityUnit = attributesArray.getDimensionPixelSize (
				R.styleable.NumberPicker_middleTextGraityUnit, 11);

		// robert diff : original is drawable but now color reference.
		int selectionDividerColor = attributesArray.getColor (R.styleable.NumberPicker_selectionDividerColor, Color.DKGRAY);

		Drawable selectionDivider = attributesArray.getDrawable (R.styleable.NumberPicker_selectionDivider);
		// if has drawable then use it , erther wise use color drawable.
		mSelectionDivider = selectionDivider != null ? selectionDivider : new ColorDrawable (selectionDividerColor);

		final int defSelectionDividerHeight = (int) TypedValue.applyDimension (
				TypedValue.COMPLEX_UNIT_DIP, UNSCALED_DEFAULT_SELECTION_DIVIDER_HEIGHT,
				getResources ().getDisplayMetrics ());
		mSelectionDividerHeight = attributesArray.getDimensionPixelSize (
				R.styleable.NumberPicker_selectionDividerHeight, defSelectionDividerHeight);

		final int defSelectionDividerDistance = (int) TypedValue.applyDimension (
				TypedValue.COMPLEX_UNIT_DIP, UNSCALED_DEFAULT_SELECTION_DIVIDERS_DISTANCE,
				getResources ().getDisplayMetrics ());
		mSelectionDividersDistance = attributesArray.getDimensionPixelSize (
				R.styleable.NumberPicker_selectionDividersDistance, defSelectionDividerDistance);

		mMinHeight = attributesArray.getDimensionPixelSize (
				R.styleable.NumberPicker_internalMinHeight, SIZE_UNSPECIFIED);

		mMaxHeight = attributesArray.getDimensionPixelSize (
				R.styleable.NumberPicker_internalMaxHeight, SIZE_UNSPECIFIED);
		if (mMinHeight != SIZE_UNSPECIFIED && mMaxHeight != SIZE_UNSPECIFIED
				&& mMinHeight > mMaxHeight) {
			throw new IllegalArgumentException ("minHeight > maxHeight");
		}

		mMinWidth = attributesArray.getDimensionPixelSize (
				R.styleable.NumberPicker_internalMinWidth, SIZE_UNSPECIFIED);

		mMaxWidth = attributesArray.getDimensionPixelSize (
				R.styleable.NumberPicker_internalMaxWidth, SIZE_UNSPECIFIED);
		if (mMinWidth != SIZE_UNSPECIFIED && mMaxWidth != SIZE_UNSPECIFIED
				&& mMinWidth > mMaxWidth) {
			throw new IllegalArgumentException ("minWidth > maxWidth");
		}

		mComputeMaxWidth = (mMaxWidth == SIZE_UNSPECIFIED);

		// diff : init self param.
		mTextSize = (int) attributesArray.getDimension (R.styleable.NumberPicker_middleTextSize, DEFAULT_INPUT_TEXT_SIZE);

		mVirtualButtonTextSize = (int) attributesArray.getDimension (R.styleable.NumberPicker_virtualButtonTextSize, DEFAULT_VIRTUAL_BUTTON_TEXT_SIZE);

		int alpha = attributesArray.getInteger (R.styleable.NumberPicker_virtualButtonTextAlpha, DEFAULT_VIRTUAL_BUTTON_TEXT_ALPHA);

		mVirtualButtonTextAlpha = (alpha < 0 || alpha > 255) ? DEFAULT_VIRTUAL_BUTTON_TEXT_ALPHA : alpha;

		//The text color for middle input text
		mTextColor = attributesArray.getColor (R.styleable.NumberPicker_middleTextColor, Color.DKGRAY);

		attributesArray.recycle ();

		mPressedStateHelper = new PressedStateHelper ();

		// By default Linearlayout that we extend is not drawn. This is
		// its draw() method is not called but dispatchDraw() is called
		// directly (see ViewGroup.drawChild()). However, this class uses
		// the fading edge effect implemented by View and we need our
		// draw() method to be called. Therefore, we declare we will draw.
		setWillNotDraw (!mHasSelectorWheel);

		LayoutInflater inflater = (LayoutInflater) getContext ().getSystemService (
				Context.LAYOUT_INFLATER_SERVICE);
		inflater.inflate (layoutResId, this, true);

		OnClickListener onClickListener = new OnClickListener () {
			public void onClick (View v) {

				if (v.getId () == R.id.np__increment) {
					changeValueByOne (true);
				} else {
					changeValueByOne (false);
				}
			}
		};

		OnLongClickListener onLongClickListener = new OnLongClickListener () {
			public boolean onLongClick (View v) {

				if (v.getId () == R.id.np__increment) {
					postChangeCurrentByOneFromLongPress (true, 0);
				} else {
					postChangeCurrentByOneFromLongPress (false, 0);
				}
				return true;
			}
		};

		// increment button
		if (!mHasSelectorWheel) {
			mIncrementButton = (ImageButton) findViewById (R.id.np__increment);
			mIncrementButton.setOnClickListener (onClickListener);
			mIncrementButton.setOnLongClickListener (onLongClickListener);
		} else {
			mIncrementButton = null;
		}

		// decrement button
		if (!mHasSelectorWheel) {
			mDecrementButton = (ImageButton) findViewById (R.id.np__decrement);
			mDecrementButton.setOnClickListener (onClickListener);
			mDecrementButton.setOnLongClickListener (onLongClickListener);
		} else {
			mDecrementButton = null;
		}

		// input text
		mCenterValueLocationHelper = (TextView) findViewById (R.id.np__numberpicker_input);
		mCenterValueLocationHelper.setTextColor (mTextColor);
		mCenterValueLocationHelper.setTextSize (TypedValue.COMPLEX_UNIT_PX, mTextSize);

		// initialize constants
		ViewConfiguration configuration = ViewConfiguration.get (context);
		mTouchSlop = configuration.getScaledTouchSlop ();
		mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity ();
		mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity ()
				/ SELECTOR_MAX_FLING_VELOCITY_ADJUSTMENT;

		// create the selector wheel paint
		Paint paint = new Paint ();
		paint.setAntiAlias (true);
		paint.setTextAlign (Align.CENTER);
		paint.setTextSize (mTextSize);
		paint.setTypeface (mCenterValueLocationHelper.getTypeface ());
		ColorStateList colors = mCenterValueLocationHelper.getTextColors ();
		int color = colors.getColorForState (ENABLED_STATE_SET, Color.WHITE);
		paint.setColor (color);
		mSelectorWheelPaint = paint;

		// create the fling and adjust scrollers
		mFlingScroller = new Scroller (getContext (), null, true);
		mAdjustScroller = new Scroller (getContext (), new DecelerateInterpolator (2.5f));

		updateInputTextView ();
	}

	@Override
	protected void onLayout (boolean changed, int left, int top, int right, int bottom) {
		if (!mHasSelectorWheel) {
			super.onLayout (changed, left, top, right, bottom);
			return;
		}
		final int msrdWdth = getMeasuredWidth ();
		final int msrdHght = getMeasuredHeight ();

		// Input text centered horizontally.
		final int inptTxtMsrdWdth = mCenterValueLocationHelper.getMeasuredWidth ();
		final int inptTxtMsrdHght = mCenterValueLocationHelper.getMeasuredHeight ();
		final int inptTxtLeft = (msrdWdth - inptTxtMsrdWdth) / 2;
		final int inptTxtTop = (msrdHght - inptTxtMsrdHght) / 2;
		final int inptTxtRight = inptTxtLeft + inptTxtMsrdWdth;
		final int inptTxtBottom = inptTxtTop + inptTxtMsrdHght;
		mCenterValueLocationHelper.layout (inptTxtLeft, inptTxtTop, inptTxtRight, inptTxtBottom);

		if (changed) {
			// need to do all this when we know our size
			initializeSelectorWheel ();
			initializeFadingEdges ();
			mTopSelectionDividerTop = (getHeight () - mSelectionDividersDistance) / 2
					- mSelectionDividerHeight;
			mBottomSelectionDividerBottom = mTopSelectionDividerTop + 2 * mSelectionDividerHeight
					+ mSelectionDividersDistance;
		}
	}

	@Override
	protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {
		if (!mHasSelectorWheel) {
			super.onMeasure (widthMeasureSpec, heightMeasureSpec);
			return;
		}
		// Try greedily to fit the max width and height.
		final int newWidthMeasureSpec = makeMeasureSpec (widthMeasureSpec, mMaxWidth);
		final int newHeightMeasureSpec = makeMeasureSpec (heightMeasureSpec, mMaxHeight);
		super.onMeasure (newWidthMeasureSpec, newHeightMeasureSpec);
		// Flag if we are measured with width or height less than the respective min.
		final int widthSize = resolveSizeAndStateRespectingMinSize (mMinWidth, getMeasuredWidth (),
				widthMeasureSpec);
		final int heightSize = resolveSizeAndStateRespectingMinSize (mMinHeight, getMeasuredHeight (),
				heightMeasureSpec);
		setMeasuredDimension (widthSize, heightSize);
	}

	/**
	 * Move to the final position of a scroller. Ensures to force finish the scroller
	 * and if it is not at its final position a scroll of the selector wheel is
	 * performed to fast forward to the final position.
	 *
	 * @param scroller The scroller to whose final position to get.
	 * @return True of the a move was performed, i.e. the scroller was not in final position.
	 */
	private boolean moveToFinalScrollerPosition (Scroller scroller) {
		scroller.forceFinished (true);
		int amountToScroll = scroller.getFinalY () - scroller.getCurrY ();
		int futureScrollOffset = (mCurrentScrollOffset + amountToScroll) % mSelectorElementHeight;
		int overshootAdjustment = mInitialScrollOffset - futureScrollOffset;
		if (overshootAdjustment != 0) {
			if (Math.abs (overshootAdjustment) > mSelectorElementHeight / 2) {
				if (overshootAdjustment > 0) {
					overshootAdjustment -= mSelectorElementHeight;
				} else {
					overshootAdjustment += mSelectorElementHeight;
				}
			}
			amountToScroll += overshootAdjustment;
			scrollBy (0, amountToScroll);
			return true;
		}
		return false;
	}

	@Override
	public boolean onInterceptTouchEvent (MotionEvent event) {
		if (!mHasSelectorWheel || !isEnabled ()) {
			return false;
		}
		// diff anything is ok ?
		final int action = event.getAction () & MotionEvent.ACTION_MASK;
		switch (action) {
			case MotionEvent.ACTION_DOWN: {
				removeAllCallbacks ();
				mCenterValueLocationHelper.setVisibility (View.INVISIBLE);
				mLastDownOrMoveEventY = mLastDownEventY = event.getY ();
				mLastDownEventTime = event.getEventTime ();
				mIngnoreMoveEvents = false;
				mShowSoftInputOnTap = false;
				// Handle pressed state before any state change.
				if (mLastDownEventY < mTopSelectionDividerTop) {
					if (mScrollState == OnScrollListener.SCROLL_STATE_IDLE) {
						mPressedStateHelper.buttonPressDelayed (
								PressedStateHelper.BUTTON_DECREMENT);
					}
				} else if (mLastDownEventY > mBottomSelectionDividerBottom) {
					if (mScrollState == OnScrollListener.SCROLL_STATE_IDLE) {
						mPressedStateHelper.buttonPressDelayed (
								PressedStateHelper.BUTTON_INCREMENT);
					}
				}
				// Make sure we support flinging inside scrollables.
				getParent ().requestDisallowInterceptTouchEvent (true);
				if (!mFlingScroller.isFinished ()) {
					mFlingScroller.forceFinished (true);
					mAdjustScroller.forceFinished (true);
					onScrollStateChange (OnScrollListener.SCROLL_STATE_IDLE);
				} else if (!mAdjustScroller.isFinished ()) {
					mFlingScroller.forceFinished (true);
					mAdjustScroller.forceFinished (true);
				} else if (mLastDownEventY < mTopSelectionDividerTop) {
					postChangeCurrentByOneFromLongPress (
							false, ViewConfiguration.getLongPressTimeout ());
				} else if (mLastDownEventY > mBottomSelectionDividerBottom) {
					postChangeCurrentByOneFromLongPress (
							true, ViewConfiguration.getLongPressTimeout ());
				} else {
					mShowSoftInputOnTap = true;
				}
				return true;
			}
		}
		return false;
	}

	@Override
	public boolean onTouchEvent (MotionEvent event) {
		if (!isEnabled () || !mHasSelectorWheel) {
			return false;
		}
		if (mVelocityTracker == null) {
			mVelocityTracker = VelocityTracker.obtain ();
		}
		mVelocityTracker.addMovement (event);
		int action = event.getAction () & MotionEvent.ACTION_MASK;
		switch (action) {
			case MotionEvent.ACTION_MOVE: {
				if (mIngnoreMoveEvents) {
					break;
				}
				float currentMoveY = event.getY ();
				if (mScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
					int deltaDownY = (int) Math.abs (currentMoveY - mLastDownEventY);
					if (deltaDownY > mTouchSlop) {
						removeAllCallbacks ();
						onScrollStateChange (OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
					}
				} else {
					int deltaMoveY = (int) ((currentMoveY - mLastDownOrMoveEventY));
					scrollBy (0, deltaMoveY);
					invalidate ();
				}
				mLastDownOrMoveEventY = currentMoveY;
			}
			break;
			case MotionEvent.ACTION_UP: {
				removeChangeCurrentByOneFromLongPress ();
				mPressedStateHelper.cancel ();
				VelocityTracker velocityTracker = mVelocityTracker;
				velocityTracker.computeCurrentVelocity (1000, mMaximumFlingVelocity);
				int initialVelocity = (int) velocityTracker.getYVelocity ();
				if (Math.abs (initialVelocity) > mMinimumFlingVelocity) {
					fling (initialVelocity);
					onScrollStateChange (OnScrollListener.SCROLL_STATE_FLING);
					Log.d ("MotionEvent", "fling ...");
				} else {
					int eventY = (int) event.getY ();
					int deltaMoveY = (int) Math.abs (eventY - mLastDownEventY);
					long deltaTime = event.getEventTime () - mLastDownEventTime;
					if (deltaMoveY <= mTouchSlop && deltaTime < ViewConfiguration.getTapTimeout ()) {
						if (mShowSoftInputOnTap) {
							mShowSoftInputOnTap = false;
							Log.d ("MotionEvent", "no scroll [1]...");
						} else {
							int selectorIndexOffset = (eventY / mSelectorElementHeight)
									- SELECTOR_MIDDLE_ITEM_INDEX;
							if (selectorIndexOffset > 0) {
								changeValueByOne (true);
								mPressedStateHelper.buttonTapped (
										PressedStateHelper.BUTTON_INCREMENT);
								Log.d ("MotionEvent", "scroll next ...");
							} else if (selectorIndexOffset < 0) {
								changeValueByOne (false);
								mPressedStateHelper.buttonTapped (
										PressedStateHelper.BUTTON_DECREMENT);
								Log.d ("MotionEvent", "scroll reset ...");
							} else {
								// TODO: 15/11/22 将Text复位...否则会出现View在滑动过程中停止
								ensureScrollWheelAdjusted ();
								Log.d ("MotionEvent", "ensureScrollWheelAdjusted[2] ...");
							}
						}
					} else {
						ensureScrollWheelAdjusted ();
						Log.d ("MotionEvent", "ensureScrollWheelAdjusted() ...");
					}
					onScrollStateChange (OnScrollListener.SCROLL_STATE_IDLE);
				}
				mVelocityTracker.recycle ();
				mVelocityTracker = null;
			}
			break;
		}
		return true;
	}

	@Override
	public boolean dispatchTouchEvent (MotionEvent event) {
		final int action = event.getAction () & MotionEvent.ACTION_MASK;
		switch (action) {
			case MotionEvent.ACTION_CANCEL:
			case MotionEvent.ACTION_UP:
				removeAllCallbacks ();
				break;
		}
		return super.dispatchTouchEvent (event);
	}

	@Override
	public boolean dispatchKeyEvent (KeyEvent event) {
		final int keyCode = event.getKeyCode ();
		switch (keyCode) {
			case KeyEvent.KEYCODE_DPAD_CENTER:
			case KeyEvent.KEYCODE_ENTER:
				removeAllCallbacks ();
				break;
			case KeyEvent.KEYCODE_DPAD_DOWN:
			case KeyEvent.KEYCODE_DPAD_UP:
				if (!mHasSelectorWheel) {
					break;
				}
				switch (event.getAction ()) {
					case KeyEvent.ACTION_DOWN:
						if (mWrapSelectorWheel || (keyCode == KeyEvent.KEYCODE_DPAD_DOWN)
								? getValue () < getMaxValue () : getValue () > getMinValue ()) {
							requestFocus ();
							mLastHandledDownDpadKeyCode = keyCode;
							removeAllCallbacks ();
							if (mFlingScroller.isFinished ()) {
								changeValueByOne (keyCode == KeyEvent.KEYCODE_DPAD_DOWN);
							}
							return true;
						}
						break;
					case KeyEvent.ACTION_UP:
						if (mLastHandledDownDpadKeyCode == keyCode) {
							mLastHandledDownDpadKeyCode = -1;
							return true;
						}
						break;
				}
		}
		return super.dispatchKeyEvent (event);
	}

	@Override
	public boolean dispatchTrackballEvent (MotionEvent event) {
		final int action = event.getAction () & MotionEvent.ACTION_MASK;
		switch (action) {
			case MotionEvent.ACTION_CANCEL:
			case MotionEvent.ACTION_UP:
				removeAllCallbacks ();
				break;
		}
		return super.dispatchTrackballEvent (event);
	}

	@Override
	protected boolean dispatchHoverEvent (MotionEvent event) {
		return !mHasSelectorWheel && super.dispatchHoverEvent (event);
	}

	@Override
	public void computeScroll () {
		Scroller scroller = mFlingScroller;
		if (scroller.isFinished ()) {
			scroller = mAdjustScroller;
			if (scroller.isFinished ()) {
				return;
			}
		}
		scroller.computeScrollOffset ();
		int currentScrollerY = scroller.getCurrY ();
		if (mPreviousScrollerY == 0) {
			mPreviousScrollerY = scroller.getStartY ();
		}
		scrollBy (0, currentScrollerY - mPreviousScrollerY);
		mPreviousScrollerY = currentScrollerY;
		if (scroller.isFinished ()) {
			onScrollerFinished (scroller);
		} else {
			invalidate ();
		}
	}

	@Override
	public void setEnabled (boolean enabled) {
		super.setEnabled (enabled);
		if (!mHasSelectorWheel) {
			mIncrementButton.setEnabled (enabled);
		}
		if (!mHasSelectorWheel) {
			mDecrementButton.setEnabled (enabled);
		}
	}

	@Override
	public void scrollBy (int x, int y) {
		int[] selectorIndices = mSelectorIndices;
		if (!mWrapSelectorWheel && y > 0
				&& selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] <= mMinValue) {
			mCurrentScrollOffset = mInitialScrollOffset;
			return;
		}
		if (!mWrapSelectorWheel && y < 0
				&& selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] >= mMaxValue) {
			mCurrentScrollOffset = mInitialScrollOffset;
			return;
		}
		mCurrentScrollOffset += y;
		while (mCurrentScrollOffset - mInitialScrollOffset > mSelectorTextGapHeight) {
			mCurrentScrollOffset -= mSelectorElementHeight;
			decrementSelectorIndices (selectorIndices);
			setValueInternal (selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX], true);
			if (!mWrapSelectorWheel && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] <= mMinValue) {
				mCurrentScrollOffset = mInitialScrollOffset;
			}
		}
		while (mCurrentScrollOffset - mInitialScrollOffset < -mSelectorTextGapHeight) {
			mCurrentScrollOffset += mSelectorElementHeight;
			incrementSelectorIndices (selectorIndices);
			setValueInternal (selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX], true);
			if (!mWrapSelectorWheel && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] >= mMaxValue) {
				mCurrentScrollOffset = mInitialScrollOffset;
			}
		}
	}

	@Override
	public int getSolidColor () {
		return mSolidColor;
	}

	/**
	 * Sets the listener to be notified on change of the current value.
	 *
	 * @param onValueChangedListener The listener.
	 */
	public void setOnValueChangedListener (OnValueChangeListener onValueChangedListener) {
		mOnValueChangeListener = onValueChangedListener;
	}

	/**
	 * Set listener to be notified for scroll state changes.
	 *
	 * @param onScrollListener The listener.
	 */
	public void setOnScrollListener (OnScrollListener onScrollListener) {
		mOnScrollListener = onScrollListener;
	}

	/**
	 * Set the formatter to be used for formatting the current value.
	 * <p>
	 * Note: If you have provided alternative values for the values this
	 * formatter is never invoked.
	 * </p>
	 *
	 * @param formatter The formatter object. If formatter is <code>null</code>,
	 *                  {@link String#valueOf(int)} will be used.
	 * @see #setDisplayedValues(String[])
	 */
	public void setFormatter (Formatter formatter) {
		if (formatter == mFormatter) {
			return;
		}
		mFormatter = formatter;
		initializeSelectorWheelIndices ();
		updateInputTextView ();
	}

	/**
	 * Set the current value for the number picker.
	 * <p>
	 * If the argument is less than the {@link NumberPicker#getMinValue()} and
	 * {@link NumberPicker#getWrapSelectorWheel()} is <code>false</code> the
	 * current value is set to the {@link NumberPicker#getMinValue()} value.
	 * </p>
	 * <p>
	 * If the argument is less than the {@link NumberPicker#getMinValue()} and
	 * {@link NumberPicker#getWrapSelectorWheel()} is <code>true</code> the
	 * current value is set to the {@link NumberPicker#getMaxValue()} value.
	 * </p>
	 * <p>
	 * If the argument is less than the {@link NumberPicker#getMaxValue()} and
	 * {@link NumberPicker#getWrapSelectorWheel()} is <code>false</code> the
	 * current value is set to the {@link NumberPicker#getMaxValue()} value.
	 * </p>
	 * <p>
	 * If the argument is less than the {@link NumberPicker#getMaxValue()} and
	 * {@link NumberPicker#getWrapSelectorWheel()} is <code>true</code> the
	 * current value is set to the {@link NumberPicker#getMinValue()} value.
	 * </p>
	 *
	 * @param value The current value.
	 * @see #setWrapSelectorWheel(boolean)
	 * @see #setMinValue(int)
	 * @see #setMaxValue(int)
	 */
	public void setValue (int value) {
		setValueInternal (value, false);
	}


	/**
	 * Computes the max width if no such specified as an attribute.
	 */
	private void tryComputeMaxWidth () {
		if (!mComputeMaxWidth) {
			return;
		}
		int maxTextWidth = 0;
		if (mDisplayedValues == null) {
			float maxDigitWidth = 0;
			for (int i = 0; i <= 9; i++) {
				final float digitWidth = mSelectorWheelPaint.measureText (formatNumberWithLocale (i));
				if (digitWidth > maxDigitWidth) {
					maxDigitWidth = digitWidth;
				}
			}
			int numberOfDigits = 0;
			int current = mMaxValue;
			while (current > 0) {
				numberOfDigits++;
				current = current / 10;
			}
			maxTextWidth = (int) (numberOfDigits * maxDigitWidth);
		} else {
			final int valueCount = mDisplayedValues.length;
			for (String mDisplayedValue : mDisplayedValues) {
				final float textWidth = mSelectorWheelPaint.measureText (mDisplayedValue);
				if (textWidth > maxTextWidth) {
					maxTextWidth = (int) textWidth;
				}
			}
		}
		maxTextWidth += mCenterValueLocationHelper.getPaddingLeft () + mCenterValueLocationHelper.getPaddingRight ();
		if (mMaxWidth != maxTextWidth) {
			if (maxTextWidth > mMinWidth) {
				mMaxWidth = maxTextWidth;
			} else {
				mMaxWidth = mMinWidth;
			}
			invalidate ();
		}
	}

	/**
	 * Gets whether the selector wheel wraps when reaching the min/max value.
	 *
	 * @return True if the selector wheel wraps.
	 * @see #getMinValue()
	 * @see #getMaxValue()
	 */
	public boolean getWrapSelectorWheel () {
		return mWrapSelectorWheel;
	}

	/**
	 * Sets whether the selector wheel shown during flinging/scrolling should
	 * wrap around the {@link NumberPicker#getMinValue()} and
	 * {@link NumberPicker#getMaxValue()} values.
	 * <p>
	 * By default if the range (max - min) is more than the number of items shown
	 * on the selector wheel the selector wheel wrapping is enabled.
	 * </p>
	 * <p>
	 * <strong>Note:</strong> If the number of items, i.e. the range (
	 * {@link #getMaxValue()} - {@link #getMinValue()}) is less than
	 * the number of items shown on the selector wheel, the selector wheel will
	 * not wrap. Hence, in such a case calling this method is a NOP.
	 * </p>
	 *
	 * @param wrapSelectorWheel Whether to wrap.
	 */
	public void setWrapSelectorWheel (boolean wrapSelectorWheel) {
		final boolean wrappingAllowed = (mMaxValue - mMinValue) >= mSelectorIndices.length;
		if ((!wrapSelectorWheel || wrappingAllowed) && wrapSelectorWheel != mWrapSelectorWheel) {
			mWrapSelectorWheel = wrapSelectorWheel;
		}
	}

	/**
	 * Sets the speed at which the numbers be incremented and decremented when
	 * the up and down buttons are long pressed respectively.
	 * <p>
	 * The default value is 300 ms.
	 * </p>
	 *
	 * @param intervalMillis The speed (in milliseconds) at which the numbers
	 *                       will be incremented and decremented.
	 */
	public void setOnLongPressUpdateInterval (long intervalMillis) {
		mLongPressUpdateInterval = intervalMillis;
	}

	/**
	 * Returns the value of the picker.
	 *
	 * @return The value.
	 */
	private int getValue () {
		return mValue;
	}

	/**
	 * Returns the min value of the picker.
	 *
	 * @return The min value
	 */
	private int getMinValue () {
		return mMinValue;
	}

	/**
	 * Sets the min value of the picker.
	 *
	 * @param minValue The min value inclusive.
	 *                 <strong>Note:</strong> The length of the displayed values array
	 *                 set via {@link #setDisplayedValues(String[])} must be equal to the
	 *                 range of selectable numbers which is equal to
	 *                 {@link #getMaxValue()} - {@link #getMinValue()} + 1.
	 */
	public void setMinValue (int minValue) {
		if (mMinValue == minValue) {
			return;
		}
		if (minValue < 0) {
			throw new IllegalArgumentException ("minValue must be >= 0");
		}
		mMinValue = minValue;
		if (mMinValue > mValue) {
			mValue = mMinValue;
		}
		// diff bu lijie ..
		boolean wrapSelectorWheel = mMaxValue - mMinValue > mSelectorIndices.length;
		setWrapSelectorWheel (wrapSelectorWheel);
		initializeSelectorWheelIndices ();
		updateInputTextView ();
		tryComputeMaxWidth ();
		invalidate ();
	}

	/**
	 * Returns the max value of the picker.
	 *
	 * @return The max value.
	 */
	private int getMaxValue () {
		return mMaxValue;
	}

	/**
	 * Sets the max value of the picker.
	 *
	 * @param maxValue The max value inclusive.
	 *
	 *                 <strong>Note:</strong> The length of the displayed values array
	 *                 set via {@link #setDisplayedValues(String[])} must be equal to the
	 *                 range of selectable numbers which is equal to
	 *                 {@link #getMaxValue()} - {@link #getMinValue()} + 1.
	 */
	public void setMaxValue (int maxValue) {
		if (mMaxValue == maxValue) {
			return;
		}
		if (maxValue < 0) {
			throw new IllegalArgumentException ("maxValue must be >= 0");
		}
		mMaxValue = maxValue;
		if (mMaxValue < mValue) {
			mValue = mMaxValue;
		}
		boolean wrapSelectorWheel = mMaxValue - mMinValue > mSelectorIndices.length;
		setWrapSelectorWheel (wrapSelectorWheel);
		initializeSelectorWheelIndices ();
		updateInputTextView ();
		tryComputeMaxWidth ();
		invalidate ();
	}

	/**
	 * Gets the values to be displayed instead of string values.
	 *
	 * @return The displayed values.
	 */
	public String[] getDisplayedValues () {
		return mDisplayedValues;
	}

	/**
	 * Sets the values to be displayed.
	 *
	 * @param displayedValues The displayed values.
	 *
	 *                        <strong>Note:</strong> The length of the displayed values array
	 *                        must be equal to the range of selectable numbers which is equal to
	 *                        {@link #getMaxValue()} - {@link #getMinValue()} + 1.
	 */
	public void setDisplayedValues (String[] displayedValues) {
		if (mDisplayedValues == displayedValues) {
			return;
		}
		mDisplayedValues = displayedValues;
		updateInputTextView ();
		initializeSelectorWheelIndices ();
		tryComputeMaxWidth ();
	}

	@Override
	protected float getTopFadingEdgeStrength () {
		return TOP_AND_BOTTOM_FADING_EDGE_STRENGTH;
	}

	@Override
	protected float getBottomFadingEdgeStrength () {
		return TOP_AND_BOTTOM_FADING_EDGE_STRENGTH;
	}

	@Override
	protected void onDetachedFromWindow () {
		super.onDetachedFromWindow ();
		removeAllCallbacks ();
	}

	@Override
	protected void onDraw (Canvas canvas) {
		if (!mHasSelectorWheel) {
			super.onDraw (canvas);
			return;
		}
		float x = (getRight () - getLeft ()) / 2;
		float y = mCurrentScrollOffset;

		// draw the selector wheel
		int[] selectorIndices = mSelectorIndices;
		for (int i = 0; i < selectorIndices.length; i++) {
			int selectorIndex = selectorIndices[i];
			String scrollSelectorValue = mSelectorIndexToStringCache.get (selectorIndex);
			// Do not draw the middle item if input is visible since the input
			// is shown only if the wheel is static and it covers the middle
			// item. Otherwise, if the user starts editing the text via the
			// IME he may see a dimmed version of the old value intermixed
			// with the new one.
			if (i != SELECTOR_MIDDLE_ITEM_INDEX || mCenterValueLocationHelper.getVisibility () != VISIBLE) {

				float size = mVirtualButtonTextSize;
				int alpha = mVirtualButtonTextAlpha;

				float maxMidUnit = 0;
				float offset = 0;
				switch (middleTextGraity) {
					case MID_TEXT_GRAITY_LEFT:
						maxMidUnit = -middleTextGraityUnit;
						break;
					case MID_TEXT_GRAITY_NORMAL:
						maxMidUnit = 0;
						break;
					case MID_TEXT_GRAITY_RIGHT:
						maxMidUnit = middleTextGraityUnit;
						break;
				}

				if (y > mInitialScrollOffset && y <= mInitialScrollOffset + mSelectorElementHeight) {
					// 变大
					size = size + (y - mInitialScrollOffset) / mSelectorElementHeight * (mTextSize - size);
					alpha = (int) (alpha + (y - mInitialScrollOffset) / mSelectorElementHeight * (255 - alpha));
					offset = offset + (y - mInitialScrollOffset) / mSelectorElementHeight * (maxMidUnit - offset);

				} else if (y > mInitialScrollOffset + mSelectorElementHeight && y < mInitialScrollOffset + mSelectorElementHeight * 2) {
					// 变小
					size = mTextSize - (y - mInitialScrollOffset - mSelectorElementHeight) / mSelectorElementHeight * (mTextSize - size);
					alpha = (int) (255 - (y - mInitialScrollOffset - mSelectorElementHeight) / mSelectorElementHeight * (255 - alpha));
					offset = maxMidUnit - (y - mInitialScrollOffset - mSelectorElementHeight) / mSelectorElementHeight * (maxMidUnit - offset);

				}
				if (alpha == 255) {
					mSelectorWheelPaint.setColor (mTextColor);
				} else {
					mSelectorWheelPaint.setColor (mUnselectedTextColor);
				}
				Log.d ("offset", "offset = " + offset);
				mSelectorWheelPaint.setTextSize (size);
				canvas.drawText (scrollSelectorValue, x + offset, y, mSelectorWheelPaint);
			}
			y += mSelectorElementHeight;
		}

		// draw the selection dividers
		if (mSelectionDivider != null) {
			// draw the top divider
			int topOfTopDivider = mTopSelectionDividerTop;
			int bottomOfTopDivider = topOfTopDivider + mSelectionDividerHeight;
			mSelectionDivider.setBounds (0, topOfTopDivider, getRight (), bottomOfTopDivider);
			mSelectionDivider.draw (canvas);

			// draw the bottom divider
			int bottomOfBottomDivider = mBottomSelectionDividerBottom;
			int topOfBottomDivider = bottomOfBottomDivider - mSelectionDividerHeight;
			mSelectionDivider.setBounds (0, topOfBottomDivider, getRight (), bottomOfBottomDivider);
			mSelectionDivider.draw (canvas);
		}
	}

//    private void setPaint(float y, Paint paint) {
//        float size = mVirtualButtonTextSize;
//        int alpha = mVirtualButtonTextAlpha;
//        if (y > mInitialScrollOffset && y <= mInitialScrollOffset + mSelectorElementHeight) {
//            size = size + (y - mInitialScrollOffset) / mSelectorElementHeight * (mTextSize - size);
//            alpha = (int) (alpha + (y - mInitialScrollOffset) / mSelectorElementHeight * (255 - alpha));
//        } else if (y > mInitialScrollOffset + mSelectorElementHeight && y < mInitialScrollOffset + mSelectorElementHeight * 2) {
//            size = mTextSize - (y - mInitialScrollOffset - mSelectorElementHeight) / mSelectorElementHeight * (mTextSize - size);
//            alpha = (int) (255 - (y - mInitialScrollOffset - mSelectorElementHeight) / mSelectorElementHeight * (255 - alpha));
//        }
//        Log.d("setPaint", "size = " + size + ", alpha = " + alpha);
//        if (alpha == 255) {
//            paint.setColor(mTextColor);
//            paint.setTextSize(mTextSize);
//        } else {
//            paint.setColor(mUnselectedTextColor);
//            paint.setTextSize(mUnselectedTextSize);
//        }
////        paint.setTextSize(size);
////        paint.setAlpha(alpha);
//    }

	/**
	 * Makes a measure spec that tries greedily to use the max value.
	 *
	 * @param measureSpec The measure spec.
	 * @param maxSize     The max value for the size.
	 * @return A measure spec greedily imposing the max size.
	 */
	private int makeMeasureSpec (int measureSpec, int maxSize) {
		if (maxSize == SIZE_UNSPECIFIED) {
			return measureSpec;
		}
		final int size = MeasureSpec.getSize (measureSpec);
		final int mode = MeasureSpec.getMode (measureSpec);
		switch (mode) {
			case MeasureSpec.EXACTLY:
				return measureSpec;
			case MeasureSpec.AT_MOST:
				return MeasureSpec.makeMeasureSpec (Math.min (size, maxSize), MeasureSpec.EXACTLY);
			case MeasureSpec.UNSPECIFIED:
				return MeasureSpec.makeMeasureSpec (maxSize, MeasureSpec.EXACTLY);
			default:
				throw new IllegalArgumentException ("Unknown measure mode: " + mode);
		}
	}

	/**
	 * Utility to reconcile a desired size and state, with constraints imposed
	 * by a MeasureSpec. Tries to respect the min size, unless a different size
	 * is imposed by the constraints.
	 *
	 * @param minSize      The minimal desired size.
	 * @param measuredSize The currently measured size.
	 * @param measureSpec  The current measure spec.
	 * @return The resolved size and state.
	 */
	private int resolveSizeAndStateRespectingMinSize (
			int minSize, int measuredSize, int measureSpec) {
		if (minSize != SIZE_UNSPECIFIED) {
			final int desiredWidth = Math.max (minSize, measuredSize);
			return resolveSizeAndState (desiredWidth, measureSpec, 0);
		} else {
			return measuredSize;
		}
	}

	// diff carefully.

	/**
	 * Utility to reconcile a desired size and state, with constraints imposed
	 * by a MeasureSpec.  Will take the desired size, unless a different size
	 * is imposed by the constraints.  The returned value is a compound integer,
	 * with the resolved size in the {@link #MEASURED_SIZE_MASK} bits and
	 * optionally the bit {@link #MEASURED_STATE_TOO_SMALL} set if the resulting
	 * size is smaller than the size the view wants to be.
	 *
	 * @param size        How big the view wants to be
	 * @param measureSpec Constraints imposed by the parent
	 * @param childMeasuredState chile measure state
	 * @return Size information bit mask as defined by
	 * {@link #MEASURED_SIZE_MASK} and {@link #MEASURED_STATE_TOO_SMALL}.
	 */
	public static int resolveSizeAndState (int size, int measureSpec, int childMeasuredState) {
		int result = size;
		int specMode = MeasureSpec.getMode (measureSpec);
		int specSize = MeasureSpec.getSize (measureSpec);
		switch (specMode) {
			case MeasureSpec.UNSPECIFIED:
				result = size;
				break;
			case MeasureSpec.AT_MOST:
				if (specSize < size) {
					result = specSize | MEASURED_STATE_TOO_SMALL;
				} else {
					result = size;
				}
				break;
			case MeasureSpec.EXACTLY:
				result = specSize;
				break;
		}
		return result | (childMeasuredState & MEASURED_STATE_MASK);
	}

	/**
	 * Resets the selector indices and clear the cached string representation of
	 * these indices.
	 */
	private void initializeSelectorWheelIndices () {
		mSelectorIndexToStringCache.clear ();
		int[] selectorIndices = mSelectorIndices;
		int current = getValue ();
		for (int i = 0; i < mSelectorIndices.length; i++) {
			int selectorIndex = current + (i - SELECTOR_MIDDLE_ITEM_INDEX);
			if (mWrapSelectorWheel) {
				selectorIndex = getWrappedSelectorIndex (selectorIndex);
			}
			selectorIndices[i] = selectorIndex;
			ensureCachedScrollSelectorValue (selectorIndices[i]);
		}
	}

	/**
	 * Sets the current value of this NumberPicker.
	 *
	 * @param current      The new value of the NumberPicker.
	 * @param notifyChange Whether to notify if the current value changed.
	 */
	private void setValueInternal (int current, boolean notifyChange) {
		if (mValue == current) {
			return;
		}
		// Wrap around the values if we go past the start or end
		if (mWrapSelectorWheel) {
			current = getWrappedSelectorIndex (current);
		} else {
			current = Math.max (current, mMinValue);
			current = Math.min (current, mMaxValue);
		}
		int previous = mValue;
		mValue = current;
		updateInputTextView ();
		if (notifyChange) {
			notifyChange (previous, current);
		}
		initializeSelectorWheelIndices ();
		invalidate ();
	}

	/**
	 * Changes the current value by one which is increment or
	 * decrement based on the passes argument.
	 * decrement the current value.
	 *
	 * @param increment True to increment, false to decrement.
	 */
	private void changeValueByOne (boolean increment) {
		if (mHasSelectorWheel) {
			mCenterValueLocationHelper.setVisibility (View.INVISIBLE);
			if (!moveToFinalScrollerPosition (mFlingScroller)) {
				moveToFinalScrollerPosition (mAdjustScroller);
			}
			mPreviousScrollerY = 0;
			if (increment) {
				mFlingScroller.startScroll (0, 0, 0, -mSelectorElementHeight, SNAP_SCROLL_DURATION);
			} else {
				mFlingScroller.startScroll (0, 0, 0, mSelectorElementHeight, SNAP_SCROLL_DURATION);
			}
			invalidate ();
		} else {
			if (increment) {
				setValueInternal (mValue + 1, true);
			} else {
				setValueInternal (mValue - 1, true);
			}
		}
	}

	private void initializeSelectorWheel () {
		initializeSelectorWheelIndices ();
		int[] selectorIndices = mSelectorIndices;
		int totalTextHeight = selectorIndices.length * mTextSize;
		float totalTextGapHeight = (getBottom () - getTop ()) - totalTextHeight;
		float textGapCount = selectorIndices.length;
		mSelectorTextGapHeight = (int) (totalTextGapHeight / textGapCount + 0.5f);
		mSelectorElementHeight = mTextSize + mSelectorTextGapHeight;
		// Ensure that the middle item is positioned the same as the text in
		// mCenterValueLocationHelper
		int editTextTextPosition = mCenterValueLocationHelper.getBaseline () + mCenterValueLocationHelper.getTop ();
		mInitialScrollOffset = editTextTextPosition
				- (mSelectorElementHeight * SELECTOR_MIDDLE_ITEM_INDEX);
		mCurrentScrollOffset = mInitialScrollOffset;
		updateInputTextView ();
	}

	private void initializeFadingEdges () {
		setVerticalFadingEdgeEnabled (false);
		setFadingEdgeLength ((getBottom () - getTop () - mTextSize) / 2);
	}

	/**
	 * Callback invoked upon completion of a given <code>scroller</code>.
	 */
	private void onScrollerFinished (Scroller scroller) {
		if (scroller == mFlingScroller) {
			if (!ensureScrollWheelAdjusted ()) {
				updateInputTextView ();
			}
			onScrollStateChange (OnScrollListener.SCROLL_STATE_IDLE);
		} else {
			if (mScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
				updateInputTextView ();
			}
		}
	}

	/**
	 * Handles transition to a given <code>scrollState</code>
	 */
	private void onScrollStateChange (int scrollState) {
		if (mScrollState == scrollState) {
			return;
		}
		mScrollState = scrollState;
		if (mOnScrollListener != null) {
			mOnScrollListener.onScrollStateChange (this, scrollState);
		}
	}

	/**
	 * Flings the selector with the given <code>velocityY</code>.
	 */
	private void fling (int velocityY) {
		mPreviousScrollerY = 0;

		if (velocityY > 0) {
			mFlingScroller.fling (0, 0, 0, velocityY, 0, 0, 0, Integer.MAX_VALUE);
		} else {
			mFlingScroller.fling (0, Integer.MAX_VALUE, 0, velocityY, 0, 0, 0, Integer.MAX_VALUE);
		}

		invalidate ();
	}

	/**
	 * @return The wrapped index <code>selectorIndex</code> value.
	 */
	private int getWrappedSelectorIndex (int selectorIndex) {
		if (selectorIndex > mMaxValue) {
			return mMinValue + (selectorIndex - mMaxValue) % (mMaxValue - mMinValue) - 1;
		} else if (selectorIndex < mMinValue) {
			return mMaxValue - (mMinValue - selectorIndex) % (mMaxValue - mMinValue) + 1;
		}
		return selectorIndex;
	}

	/**
	 * Increments the <code>selectorIndices</code> whose string representations
	 * will be displayed in the selector.
	 */
	private void incrementSelectorIndices (int[] selectorIndices) {
		for (int i = 0; i < selectorIndices.length - 1; i++) {
			selectorIndices[i] = selectorIndices[i + 1];
		}
		int nextScrollSelectorIndex = selectorIndices[selectorIndices.length - 2] + 1;
		if (mWrapSelectorWheel && nextScrollSelectorIndex > mMaxValue) {
			nextScrollSelectorIndex = mMinValue;
		}
		selectorIndices[selectorIndices.length - 1] = nextScrollSelectorIndex;
		ensureCachedScrollSelectorValue (nextScrollSelectorIndex);
	}

	/**
	 * Decrements the <code>selectorIndices</code> whose string representations
	 * will be displayed in the selector.
	 */
	private void decrementSelectorIndices (int[] selectorIndices) {
		for (int i = selectorIndices.length - 1; i > 0; i--) {
			selectorIndices[i] = selectorIndices[i - 1];
		}
		int nextScrollSelectorIndex = selectorIndices[1] - 1;
		if (mWrapSelectorWheel && nextScrollSelectorIndex < mMinValue) {
			nextScrollSelectorIndex = mMaxValue;
		}
		selectorIndices[0] = nextScrollSelectorIndex;
		ensureCachedScrollSelectorValue (nextScrollSelectorIndex);
	}

	/**
	 * Ensures we have a cached string representation of the given <code>
	 * selectorIndex</code> to avoid multiple instantiations of the same string.
	 */
	private void ensureCachedScrollSelectorValue (int selectorIndex) {
		SparseArray<String> cache = mSelectorIndexToStringCache;
		String scrollSelectorValue = cache.get (selectorIndex);
		if (scrollSelectorValue != null) {
			return;
		}
		if (selectorIndex < mMinValue || selectorIndex > mMaxValue) {
			scrollSelectorValue = "";
		} else {
			if (mDisplayedValues != null) {
				int displayedValueIndex = selectorIndex - mMinValue;
				scrollSelectorValue = mDisplayedValues[displayedValueIndex];
			} else {
				scrollSelectorValue = formatNumber (selectorIndex);
			}
		}
		cache.put (selectorIndex, scrollSelectorValue);
	}

	private String formatNumber (int value) {
		return (mFormatter != null) ? mFormatter.format (value) : formatNumberWithLocale (value);
	}

	private void validateInputTextView (View v) {
		String str = String.valueOf (((TextView) v).getText ());
		if (TextUtils.isEmpty (str)) {
			// Restore to the old value as we don't allow empty values
			updateInputTextView ();
		} else {
			// Check the new value and ensure it's in range
			int current = getSelectedPos (str);
			setValueInternal (current, true);
		}
	}

	/**
	 * Updates the view of this NumberPicker. If displayValues were specified in
	 * the string corresponding to the index specified by the current value will
	 * be returned. Otherwise, the formatter specified in {@link #setFormatter}
	 * will be used to format the number.
	 *
	 * @return Whether the text was updated.
	 */
	private boolean updateInputTextView () {
		/*
         * If we don't have displayed values then use the current number else
         * find the correct value in the displayed values for the current
         * number.
         */
		String text = (mDisplayedValues == null) ? formatNumber (mValue)
				: mDisplayedValues[mValue - mMinValue];
		if (!TextUtils.isEmpty (text) && !text.equals (mCenterValueLocationHelper.getText ().toString ())) {
			mCenterValueLocationHelper.setText (text);
			return true;
		}

		return false;
	}

	/**
	 * Notifies the listener, if registered, of a change of the value of this
	 * NumberPicker.
	 */
	private void notifyChange (int previous, int current) {
		if (mOnValueChangeListener != null) {
			mOnValueChangeListener.onValueChange (this, previous, mValue);
			Log.d ("robert", "previous = " + previous + ",mValue = " + mValue);
		}
	}

	/**
	 * Posts a command for changing the current value by one.
	 *
	 * @param increment Whether to increment or decrement the value.
	 */
	private void postChangeCurrentByOneFromLongPress (boolean increment, long delayMillis) {
		if (mChangeCurrentByOneFromLongPressCommand == null) {
			mChangeCurrentByOneFromLongPressCommand = new ChangeCurrentByOneFromLongPressCommand ();
		} else {
			removeCallbacks (mChangeCurrentByOneFromLongPressCommand);
		}
		mChangeCurrentByOneFromLongPressCommand.setStep (increment);
		postDelayed (mChangeCurrentByOneFromLongPressCommand, delayMillis);
	}

	/**
	 * Removes the command for changing the current value by one.
	 */
	private void removeChangeCurrentByOneFromLongPress () {
		if (mChangeCurrentByOneFromLongPressCommand != null) {
			removeCallbacks (mChangeCurrentByOneFromLongPressCommand);
		}
	}

	/**
	 * Removes all pending callback from the message queue.
	 */
	private void removeAllCallbacks () {
		if (mChangeCurrentByOneFromLongPressCommand != null) {
			removeCallbacks (mChangeCurrentByOneFromLongPressCommand);
		}
		mPressedStateHelper.cancel ();
	}

	/**
	 * @return The selected index given its displayed <code>value</code>.
	 */
	private int getSelectedPos (String value) {
		if (mDisplayedValues == null) {
			try {
				return Integer.parseInt (value);
			} catch (NumberFormatException e) {
				// Ignore as if it's not a number we don't care
			}
		} else {
			for (int i = 0; i < mDisplayedValues.length; i++) {
				// Don't force the user to type in jan when ja will do
				value = value.toLowerCase ();
				if (mDisplayedValues[i].toLowerCase ().startsWith (value)) {
					return mMinValue + i;
				}
			}

            /*
             * The user might have typed in a number into the month field i.e.
             * 10 instead of OCT so support that too.
             */
			try {
				return Integer.parseInt (value);
			} catch (NumberFormatException e) {

				// Ignore as if it's not a number we don't care
			}
		}
		return mMinValue;
	}

	/**
	 * The numbers accepted by the input text's {@link Filter}
	 */
	private static final char[] DIGIT_CHARACTERS = new char[]{
			// Latin digits are the common case
			'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
			// Arabic-Indic
			'\u0660', '\u0661', '\u0662', '\u0663', '\u0664', '\u0665', '\u0666', '\u0667', '\u0668'
			, '\u0669',
			// Extended Arabic-Indic
			'\u06f0', '\u06f1', '\u06f2', '\u06f3', '\u06f4', '\u06f5', '\u06f6', '\u06f7', '\u06f8'
			, '\u06f9'
	};

	/**
	 * Ensures that the scroll wheel is adjusted i.e. there is no offset and the
	 * middle element is in the middle of the widget.
	 *
	 * @return Whether an adjustment has been made.
	 */
	private boolean ensureScrollWheelAdjusted () {
		// adjust to the closest value
		int deltaY = mInitialScrollOffset - mCurrentScrollOffset;
		if (deltaY != 0) {
			mPreviousScrollerY = 0;
			if (Math.abs (deltaY) > mSelectorElementHeight / 2) {
				deltaY += (deltaY > 0) ? -mSelectorElementHeight : mSelectorElementHeight;
			}
			mAdjustScroller.startScroll (0, 0, 0, deltaY, SELECTOR_ADJUSTMENT_DURATION_MILLIS);
			invalidate ();
			return true;
		}
		return false;
	}

	class PressedStateHelper implements Runnable {
		public static final int BUTTON_INCREMENT = 1;
		public static final int BUTTON_DECREMENT = 2;

		private final int MODE_PRESS = 1;
		private final int MODE_TAPPED = 2;

		private int mManagedButton;
		private int mMode;

		public void cancel () {
			mMode = 0;
			mManagedButton = 0;
			NumberPicker.this.removeCallbacks (this);
			if (mIncrementVirtualButtonPressed) {
				mIncrementVirtualButtonPressed = false;
				invalidate (0, mBottomSelectionDividerBottom, getRight (), getBottom ());
			}
			mDecrementVirtualButtonPressed = false;
			if (mDecrementVirtualButtonPressed) {
				invalidate (0, 0, getRight (), mTopSelectionDividerTop);
			}
		}

		public void buttonPressDelayed (int button) {
			cancel ();
			mMode = MODE_PRESS;
			mManagedButton = button;
			NumberPicker.this.postDelayed (this, ViewConfiguration.getTapTimeout ());
		}

		public void buttonTapped (int button) {
			cancel ();
			mMode = MODE_TAPPED;
			mManagedButton = button;
			NumberPicker.this.post (this);
		}

		@Override
		public void run () {
			switch (mMode) {
				case MODE_PRESS: {
					switch (mManagedButton) {
						case BUTTON_INCREMENT: {
							mIncrementVirtualButtonPressed = true;
							invalidate (0, mBottomSelectionDividerBottom, getRight (), getBottom ());
						}
						break;
						case BUTTON_DECREMENT: {
							mDecrementVirtualButtonPressed = true;
							invalidate (0, 0, getRight (), mTopSelectionDividerTop);
						}
					}
				}
				break;
				case MODE_TAPPED: {
					switch (mManagedButton) {
						case BUTTON_INCREMENT: {
							if (!mIncrementVirtualButtonPressed) {
								NumberPicker.this.postDelayed (this,
										ViewConfiguration.getPressedStateDuration ());
							}
							mIncrementVirtualButtonPressed ^= true;
							invalidate (0, mBottomSelectionDividerBottom, getRight (), getBottom ());
						}
						break;
						case BUTTON_DECREMENT: {
							if (!mDecrementVirtualButtonPressed) {
								NumberPicker.this.postDelayed (this,
										ViewConfiguration.getPressedStateDuration ());
							}
							mDecrementVirtualButtonPressed ^= true;
							invalidate (0, 0, getRight (), mTopSelectionDividerTop);
						}
					}
				}
				break;
			}
		}
	}

	/**
	 * Command for changing the current value from a long press by one.
	 */
	class ChangeCurrentByOneFromLongPressCommand implements Runnable {
		private boolean mIncrement;

		private void setStep (boolean increment) {
			mIncrement = increment;
		}

		@Override
		public void run () {
			changeValueByOne (mIncrement);
			postDelayed (this, mLongPressUpdateInterval);
		}
	}

	/**
	 */
	public static class CustomEditText extends EditText {

		public CustomEditText (Context context, AttributeSet attrs) {
			super (context, attrs);
		}

		@Override
		public void onEditorAction (int actionCode) {
			super.onEditorAction (actionCode);
			if (actionCode == EditorInfo.IME_ACTION_DONE) {
				clearFocus ();
			}
		}
	}

	static private String formatNumberWithLocale (int value) {
		return String.format (Locale.getDefault (), "%d", value);
	}
}
